﻿package com.flajaxian {
	
	import flash.events.*;
	import flash.events.EventDispatcher;
	import flash.net.FileReferenceList;
	import flash.net.FileReference;
	
	
	public class FileReferenceCollection extends EventDispatcher
	{
		public static const FILES_SELECTED:String = "files_selected";
		public static const FILE_QUEUE_ENDS:String = "files_queue_ends";
		public static const UPLOAD_PROGRESS:String = "upload_progress";
		
		private var _currentIndex:Number = -1;
		private var _currentUploadingFile:FileReferenceHolder;
		private var _array:Array;
		private var _fileHashTable:Object;
		private var _fileReferenceList:Object;
		private var _bytesTotal:Number = 0;
		private var _bytesLoaded:Number = 0;
		
		private var _fileSizeLimit:Number;
		private var _queueSizeLimit:Number;
		private var _fileNumberLimit:uint;
		private var _allowedFileTypes:String;
		
		private var _uploader:FileUploader2;
		
		
		public function FileReferenceCollection(upl:FileUploader2){
			this._uploader = upl;
			this._array = [];
			this._fileHashTable = {};
			this._fileReferenceList = Params.instance.isSingleFileMode ? new FileReference() : new FileReferenceList();
			this._fileReferenceList.addEventListener(Event.SELECT, this.onSelectFiles);
            
			this._fileSizeLimit = Params.instance.maxFileSize;
			this._queueSizeLimit = Params.instance.maxFileQueueSize;
			this._fileNumberLimit = Params.instance.maxFileNumberLimit;
			this._allowedFileTypes = Params.instance.allowedFileTypes;
			
			FlashJsManager.instance.addEventListener(FlashJsManager.FILE_REMOVED, this.fileRemoved);
			FlashJsManager.instance.addEventListener(FlashJsManager.CANCEL_ALL, this.cancelAll);
		}
		
		private function fileRemoved(evt:FileUploaderEvent):void{
			var id:Number = Number(evt.state);
			var index:Number = this.findFileIndexByID(id);
			if(index >= 0){
				var frh:FileReferenceHolder = this._array[index] as FileReferenceHolder;
				if(frh.state == FileState.Uploading){
					frh.cancel();
				}
				this._bytesTotal -= frh.bytes;
				delete this._fileHashTable[this._array[index].hashKey];
				this._array.splice(index, 1);
				if(this._array.length == 0){
					this._uploader.initiateCancel();
					return;
				}
				if(frh.state == FileState.Uploading){
					this._currentIndex--;
					this.startNextFileUpload();
				}
			}
		}
		
		private function findHolderByFile(fr:FileReference):FileReferenceHolder{
			for(var i:int = 0; i < this._array.length; i++){
				if(this._array[i].file == fr) return this._array[i] as FileReferenceHolder;
			}
			return null;
		}
		
		public function cancelAll(evt:Object):void{
			this.cancel();
		}
		
		private function onSelectFiles(evt:Event):void{
			var fileList:Array = 
				Params.instance.isSingleFileMode 
					? [evt.currentTarget] 
					: evt.currentTarget.fileList;
			// if there is already selected file - remove it first
			if(Params.instance.isSingleFileMode && this._array.length == 1){
				var frh:FileReferenceHolder = this._array[0] as FileReferenceHolder;
				if(frh.state == FileState.Uploading){
					frh.cancel();
				}
				this._bytesTotal -= frh.bytes;
				delete this._fileHashTable[this._array[0].hashKey];
				this._array = [];
			}
			if(Params.instance.debug) t.obj(fileList, "onSelectFiles");
			if(this._currentIndex != -1){ this.reset(); }
			this.appendFiles(fileList);
			this.sendFileListToJavaScript();
			this.dispatchEvent(new FileUploaderEvent(FileReferenceCollection.FILES_SELECTED,this._array.length));
			
			if(Params.instance.initiateUploadOnSelect){
				this.startUpload();
			}
		}
		private function onUploadError(evt:IOErrorEvent):void{
			if(Params.instance.debug) t.obj(evt, "onUploadError");
			var holder = this.findHolderByFile(evt.target as FileReference);
			if(holder != null){
				this._bytesTotal -= holder.bytes;
				holder.setState(FileState.Error);
			}
			this.startNextFileUpload();
		}
		private function onUploadProgress(evt:ProgressEvent):void{
			var total:Number = this._bytesTotal;
			var loaded:Number = this._bytesLoaded + evt.bytesLoaded;
			var progressCoef:Number = (loaded / total);
			this.dispatchEvent(new FileUploaderEvent(FileReferenceCollection.UPLOAD_PROGRESS,progressCoef));
			
			FlashJsManager.instance.setUploadProgress(
													  total, 
													  loaded,
													  evt.bytesTotal,
													  evt.bytesLoaded
													  );
			
			if(evt.bytesTotal > 0 && evt.bytesTotal == evt.bytesLoaded){
				this.onUploadComplete({currentTarget:{size:evt.bytesTotal}});
			}
		}
		private function onUploadComplete(evt:Object):void{
			if(this._currentUploadingFile == null || this._currentUploadingFile.state >= FileState.Uploaded){
				return;
			}			
			this._bytesLoaded += evt.currentTarget.size;
			this._currentUploadingFile.setState(FileState.Uploaded);
			if(Params.instance.debug) t.obj(this._currentUploadingFile, "onUploadComplete");
			this.startNextFileUpload();
		}
		private function onHttpStatus(evt:HTTPStatusEvent):void{
			var holder = this.findHolderByFile(evt.target as FileReference);
			if(holder != null){
				holder.setState(FileState.Error);
				holder.setHttpStatus(evt.status);
			}
			if(Params.instance.debug) t.obj(evt, "onHttpStatus");
		}
		
		private function sendFileListToJavaScript():void{
			FlashJsManager.instance.setFileList(this.toJson());
		}
		
		private function sendChangedFileStatesToJavaScript(arr:Array, isLast:Boolean = false):void{
			var sb = [];
			sb.push("[");
			for(var i:int = 0; i < arr.length; i++){
				var frh:FileReferenceHolder = FileReferenceHolder(arr[i]);
				if(i > 0) sb.push(",");
				sb.push("{id:"+frh.id+",state:"+frh.state+",httpStatus:"+frh.httpStatus+",isLast:"+isLast+"}");
			}
			sb.push("]");
			FlashJsManager.instance.setChangedFileStates(sb.join(""));
		}
		
		
		
		
		private function appendFiles(incomingArray:Array):void{
			var maxQueueSizeIsReached:Boolean = false;
			var maxFileSizeIsReached:Boolean = false;
			var maxFileNumberIsReached:Boolean = false;
			for(var i:int = 0; i < incomingArray.length; i++){
				
				try{ // no files with size = 0 are allowed
					var temp:Number = incomingArray[i].size;
					if(temp == 0){
						continue;
					}
				}catch(e:Error){
					continue;
				}
				
				var frh:FileReferenceHolder = 
					new FileReferenceHolder(this, incomingArray[i]);
					
				// we check to see if the same file has already been chosen
				// if it was chosen skip it
				if(!this._fileHashTable.hasOwnProperty(frh.hashKey)){
					frh.file.addEventListener(IOErrorEvent.IO_ERROR, this.onUploadError);
					frh.file.addEventListener(ProgressEvent.PROGRESS, this.onUploadProgress);
					//frh.file.addEventListener(Event.COMPLETE, this.onUploadComplete);
					frh.file.addEventListener(HTTPStatusEvent.HTTP_STATUS, this.onHttpStatus);
					
					maxQueueSizeIsReached = ((this._bytesTotal + frh.bytes) > this._queueSizeLimit);
					maxFileSizeIsReached = (frh.bytes > this._fileSizeLimit);
					maxFileNumberIsReached = ((this._array.length + 1) > this._fileNumberLimit);
					
					if(maxFileNumberIsReached){
						this.callJsOnMaxFileNumberReached();
						return;
					} else if (maxFileSizeIsReached){
						this.callJsOnMaxFileSizeReached();
						return;
					} else if(maxQueueSizeIsReached){
						this.callJsOnMaxQueueSizeReached();
						return;
					}
					this._bytesTotal += frh.bytes;
					this._fileHashTable[frh.hashKey] = frh;
					this._array.push(frh);
				}
			}
		}
		
		private function callJsOnMaxFileNumberReached():void{
			FlashJsManager.instance.maxFileNumberReached();
		}
		
		private function callJsOnMaxFileSizeReached():void{
			FlashJsManager.instance.maxFileSizeReached();
		}
		
		private function callJsOnMaxQueueSizeReached():void{
			FlashJsManager.instance.maxQueueSizeReached();
		}
		
		private function reset():void{
			this._currentIndex = -1;
			this._currentUploadingFile = null;
			this._bytesTotal = 0;
			this._bytesLoaded = 0;
			this._fileHashTable = {}
			this._array = [];
			Params.instance.resetStatePerFile();
		}
		
		private function startNextFileUpload():void{
			if((this._currentIndex + 1) >= this._array.length){
				this.fileQueueEnds();
				return;
			}
			var filesWithChangedState = [];
			if(this._currentUploadingFile != null){
				filesWithChangedState.push(this._currentUploadingFile);
			}
			this._currentIndex++;
			var frh:FileReferenceHolder = FileReferenceHolder(this._array[this._currentIndex]);
			this._currentUploadingFile = frh;
			this._currentUploadingFile.setState(FileState.Uploading);
			
			filesWithChangedState.push(frh);
			this.sendChangedFileStatesToJavaScript(filesWithChangedState);
			frh.upload(this._uploader.getFileUploadRequest(frh, this._currentIndex, (this._currentIndex + 1) == this._array.length), Params.instance.uploadDataFieldName);
		}
		
		private function fileQueueEnds():void{
			if(this._array.length > 0){
				this.sendChangedFileStatesToJavaScript([this._array[this._array.length - 1]], true);
			}
			this.dispatchEvent(new FileUploaderEvent(FileReferenceCollection.FILE_QUEUE_ENDS));
			//this.reset();
		}
		
		/*public function stateChanged(holder:FileReferenceHolder):void{
			
		}*/
		
		public function startUpload():void{
			if(this._array.length == 0){
				this.fileQueueEnds();
				return;
			}
			this.startNextFileUpload();
		}
		
		public function cancel():void{
			if(this._currentUploadingFile != null){
				this._currentUploadingFile.cancel();
			}
			this.reset();
			this.sendFileListToJavaScript();
		}
		
		public function browse():void{
			if(Util.isNullOrEmpty(this._allowedFileTypes)){
				this._fileReferenceList.browse();
			} else {
				this._fileReferenceList.browse(Util.fileTypesToFileFilterArray(this._allowedFileTypes));
			}
		}
		
		public function findFileIndexByID(id:Number):Number{
			for(var i:Number = 0; i < this._array.length; i++){
				if(this._array[i].id == id){
					return i;
				}
			}
			return -1;
		}
		
		public function toJson():String{
			var sb:Array = [];
			sb.push("[");
			for(var i:Number = 0; i < this._array.length; i++){
				var frh:FileReferenceHolder = FileReferenceHolder(this._array[i]);
				if(i > 0) sb.push(",");
				sb.push(frh.toJson());
			}
			sb.push("]");
			return sb.join("");
		}
		
		public function get count():int{
			return this._array.length;
		}
		
	}
}